﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.IO;
using System.Runtime.Serialization.Formatters.Binary;

namespace NeurOthello.NNetwork
{
    [Serializable]
    public class Network
    {
        OutputNeuron[] outputs;
        InputNode[] inputs;
        Neuron[][] hidden;
        

        public Network(int nInputs, int nOutputs, int[] hiddenLayers)
        {
            outputs = new OutputNeuron[nOutputs];
            for (int i = 0; i < nOutputs; i++)
                outputs[i] = new OutputNeuron(hiddenLayers[hiddenLayers.Length-1], i);

            hidden = new Neuron[hiddenLayers.Length][];
            
            if (hiddenLayers.Length > 1)
            {
                hidden[hiddenLayers.Length - 1] = new Neuron[hiddenLayers[hiddenLayers.Length - 1]];
                for (int j = 0; j < hiddenLayers[hiddenLayers.Length - 1]; j++)
                    hidden[hiddenLayers.Length - 1][j] = new Neuron(hiddenLayers[hiddenLayers.Length - 2], j, outputs);
                
                for (int i = hiddenLayers.Length - 2; i > 0; i--)
                {
                    hidden[i] = new Neuron[hiddenLayers[i]];

                    for (int j = 0; j < hiddenLayers[i]; j++)
                        hidden[i][j] = new Neuron(hiddenLayers[i - 1], j, hidden[i + 1]);
                }
                hidden[0] = new Neuron[hiddenLayers[0]];
                for (int j = 0; j < hiddenLayers[0]; j++)
                    hidden[0][j] = new Neuron(nInputs, j, hidden[1]);
            }
            else
            {
                hidden[0] = new Neuron[hiddenLayers[0]];
                for (int j = 0; j < hiddenLayers[0]; j++)
                    hidden[0][j] = new Neuron(nInputs, j, outputs);
            }          
            inputs = new InputNode[nInputs];
            for (int j = 0; j < nInputs; j++)
                inputs[j] = new InputNode(j, hidden[0]);
        }


        public Network(int inP, int outP)
        {
            outputs = new OutputNeuron[outP];
            for (int i = 0; i < outP; i++)
                outputs[i] = new OutputNeuron(4, i);
            
            inputs = new InputNode[inP];
            for (int j = 0; j < inP; j++)
                inputs[j] = new InputNode(j, outputs);
        }

        public Network(int hidden)
            : this(130, 2, new int[1] { hidden })
        {}

        public double[] ProcessInput(double[] inputVals)
        {
            for (int i = 0; i < inputs.Length; i++)
                inputs[i].RecieveInput(inputVals[i]);
            foreach (InputNode inNode in inputs)
                inNode.Activate();
            if (hidden != null) 
                for (int j = 0; j < hidden.Length; j++)
                    foreach (Neuron node in hidden[j])
                        node.Activate();
            double[] values = new double[outputs.Length];
            for (int i = 0; i < outputs.Length; i++)
            {
                outputs[i].Activate();
                values[i] = outputs[i].outVal;
            }
            return values;
        }

        public void ProcessInput(double[] inputVals, double[] TValues, double learningR)
        {
            double[] OValues = ProcessInput(inputVals);

            if (TValues.Length != OValues.Length)
                throw new ArgumentException("Output length and YValue length doesn't match");

            for (int i = 0; i < OValues.Length; i++)
                outputs[i].BlameOutput(TValues[i]);

            if (hidden != null)
                for (int i = hidden.Length - 1; i >= 0; i--)
                    for (int j = hidden[i].Length - 1; j >= 0; j--) 
                    {
                        hidden[i][j].CalcNodeBlame();
                    }

            foreach (Neuron n in outputs)
                n.AdjustWeights(learningR);
            if (hidden != null)
                foreach (Neuron[] nh in hidden)
                    foreach (Neuron n in nh)
                        n.AdjustWeights(learningR);
        }

        public string GetNetworkLine()
        {
            string line = "";
            line += inputs.Length + ";";
            line += outputs.Length;
            if (hidden != null)
                for (int i = 0; i < hidden.Length; i++)
                    line += ";" + hidden[i].Length;
            line += "i";
            foreach (Neuron n in outputs)
                line += n.GetNeuronLine();
            line += "l";
            if (hidden != null)
                foreach (Neuron[] hl in hidden)
                {
                    foreach (Neuron n in hl)
                        line += n.GetNeuronLine();
                    line += "l";
                }
            return line;
        }

        public static Network ReadNetworkLine(string line)
        {
            string[] split1 = line.Split('i');
            string[] nInfo = split1[0].Split(';');

            int nInp = Convert.ToInt32(nInfo[0]);
            int nOutp = Convert.ToInt32(nInfo[1]);

            int[] hidden = new int[nInfo.Length - 2];
            for (int i = 0; i < nInfo.Length - 2; i++)            
                hidden[i] = Convert.ToInt32(nInfo[i + 2]);

            Network res = new Network(nInp, nOutp, hidden);

            string[] layerInfo = split1[1].Split('l');
            string[] oLayer = layerInfo[0].Split('n');
            for (int i = 0; i < res.outputs.Length; i++)
                res.outputs[i].ReadNeuronLine(oLayer[i+1]);

            for (int i = 0; i < res.hidden.Length; i++)
            {
                string[] hidInfo = layerInfo[i + 1].Split('n');
                for (int j = 0; j < res.hidden[i].Length; j++)
                    res.hidden[i][j].ReadNeuronLine(hidInfo[j+1]);
            }
            return res;
        }
        
        public void Save(string filename)
        {
            TextWriter tw = new StreamWriter(filename);     
            tw.WriteLine(GetNetworkLine()); 
            tw.Close();
        }

        public static Network Load(string filename)
        {
            TextReader tr = new StreamReader(filename);
            Network res = ReadNetworkLine(tr.ReadToEnd());
            tr.Close();
            return res;
        }

        public byte[] GetNetworkByteGene()
        {
            int nLocus = 0;
            nLocus += outputs.Length * outputs[0].NWeights;
            foreach (Neuron[] nl in hidden)
                nLocus += nl.Length * nl[0].NWeights;
            byte[] gene = new byte[nLocus];
            int curLocus = 0;
            foreach (Neuron n in outputs)
            {
                byte[] temp = n.GetNeuronByteGene();
                for (int i = 0; i < temp.Length; i++)
                {
                    gene[curLocus] = temp[i];
                    curLocus++;
                }

            }
            foreach (Neuron[] nl in hidden)
            {
                foreach (Neuron n in nl)
                {
                    byte[] temp = n.GetNeuronByteGene();
                    for (int i = 0; i < temp.Length; i++)
                    {
                        gene[curLocus] = temp[i];
                        curLocus++;
                    }

                }
            }
            return gene;
        }

        public void ReadNetworkByteGene(byte[] gene)
        {            
            int curLocus = 0;
            foreach (Neuron n in outputs)
            {    
                byte[] temp = new byte[n.NWeights];
                Array.Copy(gene, curLocus, temp, 0, n.NWeights);
                n.ReadNeuronGene(temp);
                curLocus += n.NWeights;
            }
            foreach (Neuron[] nl in hidden)
            {
                foreach (Neuron n in nl)
                {
                    byte[] temp = new byte[n.NWeights];
                    Array.Copy(gene, curLocus, temp, 0, n.NWeights);
                    n.ReadNeuronGene(temp);
                    curLocus += n.NWeights;
                }
            }
        }

        public static Network LoadNetwork(string filename)
        {
            Stream stream = null;
            try
            {
                stream = File.Open(filename, FileMode.Open);
            }
            catch (Exception) { }
            return LoadNetwork(stream);

        }
        public static Network LoadNetwork(Stream stream)
        {
            if (stream == null)
                return new Network(130, 2, new int[1] { 80 });

            BinaryFormatter bformatter = new BinaryFormatter();

            Console.WriteLine("Reading NN Information");
            Network network = (Network)bformatter.Deserialize(stream);
            stream.Close();
            return network;
        }

        public static void SaveNetwork(string filename, Network network)
        {
            Stream stream = File.Open(filename, FileMode.Create);
            BinaryFormatter bformatter = new BinaryFormatter();

            Console.WriteLine("Writing Network Information");
            bformatter.Serialize(stream, network);
            stream.Close();           
        }
    }
}
